package com.dospyer.jsql.lock;

/**
 * @author : peigen
 * Date: 2021/11/16 11:18 上午
 */
public class LockDemo {
    /**
     * 引用自极客时间 MySQL实战45讲
     * 我总结的加锁规则里面，包含了两个“原则”、两个“优化”和一个“bug”。
     * 原则 1：加锁的基本单位是 next-key lock。希望你还记得，next-key lock 是前开后闭区间。
     * 原则 2：查找过程中访问到的对象才会加锁。
     * 优化 1：索引上的等值查询，给唯一索引加锁的时候，next-key lock 退化为行锁。
     * 优化 2：索引上的等值查询，向右遍历时且最后一个值不满足等值条件的时候，next-key lock 退化为间隙锁。
     * 一个 bug：唯一索引上的范围查询会访问到不满足条件的第一个值为止。
     */

    /**
     *
     * gap锁的功能就是每当有别的事务插入记录时，会检查一下待插入记录的下一条记录上是否已经有一个gap锁的锁结构，如果有的话就进入阻塞状态。
     *
     * 前置知识：
     * 一条 sql 语句加锁的影响因素：
     * •事务的隔离级别
     * •语句执行时使用的索引类型（比如聚簇索引、唯一二级索引、普通二级索引）
     * •是否是精确匹配
     * •是否是唯一性搜索
     * •具体执行的语句类型（SELECT、INSERT、DELETE、UPDATE）
     * •是否开启innodb_locks_unsafe_for_binlog系统变量
     * •记录是否被标记删除
     *
     * 扫描区间、精确匹配、唯一性搜索
     *
     * 在扫描某个扫描区间的记录前，就能事先确定该扫描区间最多只包含1条记录的话，那么就把这种情况称作唯一性搜索。
     *
     * 我们知道MySQL其实是分成server层和存储引擎层两部分，每当执行一个查询时，server层负责生成执行计划，即选取即将使用的索引以及对应的扫描区间。
     *
     * 我们这里以 InnoDB 为例，针对每一个扫描区间，都会：
     * •server层向InnoDB要扫描区间的第1条记录
     * •InnoDB通过B+树定位到扫描区间的第1条记录（如果定位的是二级索引记录并有回表需求则回表获取完整的聚簇索引记录），然后返回给server层
     * •server层判断记录是否符合搜索条件，如果符合则发送给客户端，不符合则跳过。继续向InnoDB要下一条记录。
     * •InnoDB根据记录的单向链表以及页面之间的双向链表找到下一条记录（如果定位的是二级索引记录并有回表需求则回表获取完整的聚簇索引记录），返回给server层。
     * •server层处理该记录，并向InnoDB要下一条记录
     * •... 不停执行上述过程，直到InnoDB读到一条不符合边界条件的记录为止
     */

    /**
     * row_search_mvcc:
     * <p>
     * 一般情况下，server层和存储引擎层是以记录为单位进行通信的，而InnoDB读取一条记录最重要的函数就是row_search_mvcc：
     * <p>
     * 在row_search_mvcc里，对一条记录进行诸如多版本的可见性判断，要不要对记录进行加锁的判断，要是加锁的话加什么锁的选择，完成记录从InnoDB的存储格式到server层存储格式的转换等等等等十分繁杂的工作。
     * 其实对于UPDATE、DELETE语句来说，执行它们前都需要先在B+树中定位到相应的记录，所以它们也会调用row_search_mvcc。
     *
     * set_also_gap_locks变量：是否要给记录添加gap锁，默认值是TRUE
     * 当前执行的是SELECT ... LOCK IN SHARE MODE或者SELECT ... FOR UPDATE这样的加锁读语句（非DELETE或UPDATE语句），并且隔离级别不大于READ COMMITTED 时，将set_also_gap_locks设置为FALSE。
     *
     *
     * 在查询开启前需要生成ReadView：
     * Repeatable Read隔离级别来说，只在首次执行SELECT语句时生成ReadView，之后的SELECT语句都复用这个ReadView；
     * 对于Read Committed隔离级别来说，每次执行SELECT语句时都会生成一个ReadView。
     *
     * 在首次读取记录（prebuilt->sql_stat_start表示是否是首次读取）前，需要添加表级别的意向锁（IS或IX锁）
     */

    /**
     * 真正处理记录并给记录加锁的流程：
     * 1. 定位扫描区间的第一条记录，btr_pcur_open_with_no_init 是用于定位扫描区间中的第一条记录的函数。
     *
     * 2. 对于ORDER BY ... DESC条件形成的扫描区间的第一条记录的处理。
     *      对于 ORDER BY DESC 这种 从右向左扫描扫描区间中记录的情况，针对从扫描区间中定位到的最右边的那条记录，需要做如下处理：
     *      对于加锁读来说，在隔离级别不小于REPEATABLE READ 并且也没有开启innodb_locks_unsafe_for_binlog 系统变量的情况下，会对扫描区间中最右边的那条记录的下一条记录加一个类型为LOCK_GAP的锁，这个类型为LOCK_GAP的锁其实就是gap锁。
     *
     * 3. 对Infimum和Supremum记录的处理
     *      如果当前读取的记录是Infimum记录，则啥也不做，直接去读下一条记录。
     *      如果当前读取的记录是Supremum记录，则在下边这些条件成立的时候就会为记录添加一个类型为LOCK_ORDINARY的锁，其实也就是next-key锁。
     *          •set_also_gap_locks是TRUE（这个变量只在前边设置过，当隔离级别不大于READ COMMITTED的SELECT语句的加锁读会设置为FALSE，否则为TRUE）
     *          •未开启innodb_locks_unsafe_for_binlog系统变量并且事务的隔离级别不小于REPEATABLE READ。
     *          •本次读取属于加锁读
     *          •所使用的不是空间索引。
     *      其实由于Supremum记录本身是一条伪记录，别的事务并不会更新或删除它，所以给它添加next-key锁起到的效果和给它添加gap锁是一样的。
     *
     *  4. 对精确匹配的特殊处理
     *      对于匹配模式是精确匹配的扫描区间来说，首先会读取到符合条件的第一条记录，首先对这条记录加锁，读取完这条记录之后，InnoDB会根据记录的next_record属性找到下一条二级索引记录，该记录不在扫描区间中，这个时候会对记录加一个gap锁，并向server层报告当前扫描区间的记录都已经扫描完了
     */


    /**
     * InnoDB 存储引擎锁结构
     */
    private static class LockInfo {
        /**
         * 锁所在的事务
         */
        Long trxId;

        /**
         * 索引信息
         */
        Long index;

        /**
         * 锁信息
         */
        Lock lock;

        /**
         * 区分 lock 这个锁结构到底是行锁还是表锁
         * 如果是表锁的话是意向锁、直接对表加锁、还是 AUTO-INC 锁
         * 如果是行锁的话，具体是正经记录锁、gap 锁 还是 next-key 锁
         * <p>
         * 在InnoDB的实现中，InnoDB的行锁是与记录一一对应的。
         * 即使是对于gap锁来说，在实现上也是为某条记录生成一个锁结构，然后该锁结构的类型是gap锁而已，并不是专门为某个区间生成一个锁结构。
         * 该gap锁的功能就是每当有别的事务插入记录时，会检查一下待插入记录的下一条记录上是否已经有一个gap锁的锁结构，如果有的话就进入阻塞状态。
         * <p>
         * 我们平时所说的加锁就是在内存中生成这样的一个锁结构（除了生成锁结构，还有一种称作隐式锁的加锁方式，不用生成锁结构）。
         */
        Integer typeMode;
    }

    interface Lock {

    }

    /**
     * 表锁信息
     */
    private static class LockTab implements Lock {
        /**
         * 表名称
         */
        String tableName;

        /**
         * 其他信息
         */
        String other;

    }

    /**
     * 行锁信息
     */
    private static class LockRec implements Lock {

        String spaceId;

        String pageNumber;

    }
}
